package com.founder.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.founder.config.AliPayConfig;
import com.founder.core.constant.PayConstant;
import com.founder.core.domain.MchInfo;
import com.founder.core.domain.PayChannel;
import com.founder.core.domain.PayOrder;
import com.founder.core.domain.RefundOrder;
import com.founder.core.log.MyLog;
import com.founder.core.utils.PayDigestUtil;
import com.founder.core.utils.PayUtil;
import com.founder.service.*;
import com.founder.service.channel.PayChannel4WxService;
import com.founder.service.mq.Mq4PayNotify;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.service.WxPayService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 回调处理服务
 * 需要签名验证 防止假通知造成损失
 */
@Service
public class PayNotifyServiceImpl extends PayChannel4WxService implements IPayNotifyService {

    private static final MyLog _log = MyLog.getLog(PayNotifyServiceImpl.class);

    @Autowired
    AliPayConfig aliPayConfig;

    @Autowired
    IMchInfoService mchInfoService;

    @Autowired
    IPayOrderService payOrderService;

    @Autowired
    IRefundOrderService refundOrderService;

    @Autowired
    IPayChannelService payChannelService;

    @Autowired
    Mq4PayNotify mq4PayNotify;

    public String handleAliPayNotify(String mchId, String channelId, Map params) {
        String logPrefix = "【处理支付宝支付回调】";
        _log.info("====== 开始处理支付宝支付回调通知 ======");

        String errorMessage;

        String out_trade_no = String.valueOf(params.get("out_trade_no"));        // 商户订单号
        String total_amount = String.valueOf(params.get("total_amount"));        // 支付金额

        if (StringUtils.isEmpty(out_trade_no)) {
            _log.error("商户订单号{}为空", out_trade_no);
            return PayConstant.RETURN_VALUE_FAIL;
        }
        if (StringUtils.isEmpty(total_amount)) {
            _log.error("支付金额{}为空", total_amount);
            return PayConstant.RETURN_VALUE_FAIL;
        }

        String payOrderId = out_trade_no;
        PayOrder payOrder = payOrderService.selectPayOrder(payOrderId);
        if (payOrder == null) {
            errorMessage = "支付订单" + payOrderId + "不存在";
            _log.error("支付宝回调验证失败：{}", errorMessage);
            return PayConstant.RETURN_VALUE_FAIL;
        }

        PayChannel payChannel = payChannelService.selectPayChannel(channelId, mchId);
        if (payChannel == null) {
            errorMessage = "支付渠道" + channelId + "不存在";
            _log.error("支付宝回调验证失败：{}", errorMessage);
            return PayConstant.RETURN_VALUE_FAIL;
        }

        boolean verify_result = false;
        try {
            aliPayConfig.init(payChannel.getParam());
            verify_result = AlipaySignature.rsaCheckV1(params, aliPayConfig.init(payChannel.getParam()).getAlipay_public_key(), aliPayConfig.CHARSET, aliPayConfig.getSign_type());
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }

        _log.info("验证签名");
        if (!verify_result) {
            _log.error("支付宝回调签名验证失败");
            return PayConstant.RETURN_VALUE_FAIL;
        }

        _log.info("核对金额");
        long aliPayAmt = new BigDecimal(total_amount).movePointRight(2).longValue();
        long dbPayAmt = payOrder.getAmount().longValue();
        if (dbPayAmt != aliPayAmt) {
            _log.error("支付金额{}验证失败", aliPayAmt);
            return PayConstant.RETURN_VALUE_FAIL;
        }

        _log.info("{}验证支付通知数据及签名通过", logPrefix);
        doNotify(payOrder, "2");
        _log.info("====== 完成处理支付宝支付回调通知 ======");

        return PayConstant.RETURN_VALUE_SUCCESS;
    }


    /**
     * 异步回调接口代码内部会自动校验结果签名和业务代码
     *
     * @param xmlResult
     * @return
     */
    public String handleWxPayNotify(String mchId, String channelId, String pay, String xmlResult) {
        String logPrefix = "【处理微信回调】";

        _log.info("====== 开始处理微信回调通知 ======");
        String errorMessage = "";
        try {
            Object object = doValidWxNotify(mchId, channelId, pay, xmlResult);
            if (object instanceof String){
                return WxPayNotifyResponse.fail(object.toString());
            }

            _log.info("根据支付类型处理业务订单");
            if (object instanceof PayOrder) {
                _log.info("{}微信支付通知回调", logPrefix);
                PayOrder payOrder = (PayOrder)object;
                doNotify(payOrder, "2");
                _log.info("====== 完成处理微信支付回调通知 ======");
            } else if (object instanceof RefundOrder){
                _log.info("{}微信退款通知回调", logPrefix);
                RefundOrder refundOrder = (RefundOrder)object;
                doNotify(refundOrder, "1");
                _log.info("====== 完成处理微信退款回调通知 ======");
            }

            return WxPayNotifyResponse.success("处理成功!");
        } catch (Exception e) {
            e.printStackTrace();
            _log.error("微信回调结果异常,异常原因{}", e.getMessage());
            return WxPayNotifyResponse.fail(e.getMessage());
        }
    }

    private Object doValidWxNotify(String mchId, String channelId, String pay, String xmlResult){
        String errorMessage = "";
        try {
            PayChannel payChannel = payChannelService.selectPayChannel(channelId, mchId);
            if (payChannel == null) {
                errorMessage = "支付渠道" + channelId + "不存在";
                _log.error("支付宝回调验证失败：{}", errorMessage);
                return errorMessage;
            }

            if (PayConstant.TRADE_TYPE_PAY.equalsIgnoreCase(pay)){
                WxPayService wxPayService = super.buildWxpayService(channelId, mchId, pay);
                WxPayOrderNotifyResult result = wxPayService.parseOrderNotifyResult(xmlResult);
                String payOrderId = result.getOutTradeNo();
                _log.info("支付单号：{}", payOrderId);

                PayOrder payOrder = payOrderService.selectPayOrder(payOrderId);
                if (payOrder == null) {
                    errorMessage = "支付订单" + payOrderId + "不存在";
                    _log.error("微信回调验证失败：{}", errorMessage);
                    return errorMessage;
                }

                String tradeNo = result.getTransactionId();
                String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
                _log.info("回调返回订单号：{}，微信单号{}，金额：{}", payOrderId, tradeNo, totalFee);

                if (StringUtils.isNotBlank(payOrder.getChannelOrderNo()) && tradeNo.equalsIgnoreCase(payOrder.getChannelOrderNo())) {
                    errorMessage = "微信订单号" + tradeNo + "与业务系统不一致";
                    _log.error(errorMessage);
                    return errorMessage;
                }

                if (payOrder.getAmount().intValue() != result.getTotalFee()) {
                    errorMessage = "微信订单支付金额" + totalFee + "与业务系统不一致";
                    _log.error(errorMessage);
                    return errorMessage;
                }

                _log.info("验证微信通知数据及签名通过");
                return payOrder;
            } else if (PayConstant.TRADE_TYPE_REFUND.equalsIgnoreCase(pay)){
                WxPayService wxPayService = super.buildWxpayService(channelId, mchId, pay);
                WxPayRefundNotifyResult result = wxPayService.parseRefundNotifyResult(xmlResult);
                String refundOrderId = result.getReqInfo().getOutRefundNo();
                _log.info("退款单号：{}", refundOrderId);

                RefundOrder refundOrder = refundOrderService.selectRefundOrder(refundOrderId);
                if (refundOrder == null) {
                    errorMessage = "退款订单" + refundOrder + "不存在";
                    _log.error("微信回调验证失败：{}", errorMessage);
                    return errorMessage;
                }

                String tradeNo = result.getReqInfo().getRefundId();
                String totalFee = BaseWxPayResult.fenToYuan(result.getReqInfo().getRefundFee());
                _log.info("回调返回订单号：{}，微信单号{}，金额：{}", refundOrderId, tradeNo, totalFee);

                _log.info("验证微信退款通知数据及签名通过");
                return refundOrder;
            } else {
                errorMessage = "未知的支付类型：" + pay;
                return errorMessage;
            }

        } catch (Exception e) {
            e.printStackTrace();
            _log.error("微信回调结果异常,异常原因{}", e.getMessage());
            errorMessage = e.getMessage();
            return errorMessage;
        }
    }

    /**
     * 处理支付结果后台服务器通知
     */
    private void doNotify(PayOrder payOrder, String backType) {
        _log.info(">>>>>> PAY开始回调通知业务系统 <<<<<<");
        JSONObject object = createNotifyInfo(payOrder, backType);
        try {
            _log.info("支付后发起后台通知业务系统");
            mq4PayNotify.send(object.toJSONString());
        } catch (Exception e) {
            _log.error("payOrderId={},sendMessage error.", payOrder != null ? payOrder.getPayOrderId() : "", e);
        }
        _log.info(">>>>>> PAY回调通知业务系统完成 <<<<<<");
    }

    /**
     * 处理退款结果后台服务器通知
     */
    private void doNotify(RefundOrder refundOrder, String backType) {
        _log.info(">>>>>> PAY开始回调通知业务系统 <<<<<<");
        JSONObject object = createNotifyInfo(refundOrder, backType);
        try {
            _log.info("退款后发起后台通知业务系统");
            mq4PayNotify.send(object.toJSONString());
        } catch (Exception e) {
            _log.error("payOrderId={},sendMessage error.", refundOrder != null ? refundOrder.getRefundOrderId() : "", e);
        }
        _log.info(">>>>>> PAY回调通知业务系统完成 <<<<<<");
    }

    private JSONObject createNotifyInfo(PayOrder payOrder, String backType) {
        JSONObject object = new JSONObject();
        object.put("method", "GET");
        object.put("url", createNotifyUrl(payOrder, backType));
        object.put("orderId", payOrder.getPayOrderId());
        object.put("count", payOrder.getNotifyCount());
        object.put("createTime", System.currentTimeMillis());
        return object;
    }

    private JSONObject createNotifyInfo(RefundOrder refundOrder, String backType) {
        JSONObject object = new JSONObject();
        object.put("method", "GET");
        object.put("url", createNotifyUrl(refundOrder, backType));
        object.put("orderId", refundOrder.getPayOrderId());
        object.put("refundOrderId", refundOrder.getRefundOrderId());
        object.put("count", 1);
        object.put("createTime", System.currentTimeMillis());
        return object;
    }

    private String createNotifyUrl(PayOrder payOrder, String backType) {
        String mchId = payOrder.getMchId();
        MchInfo mchInfo = mchInfoService.selectMchInfo(mchId);
        String resKey = mchInfo.getResKey();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("payOrderId", payOrder.getPayOrderId() == null ? "" : payOrder.getPayOrderId());           // 支付订单号
        paramMap.put("mchId", payOrder.getMchId() == null ? "" : payOrder.getMchId());                        // 商户ID
        paramMap.put("mchOrderNo", payOrder.getMchOrderNo() == null ? "" : payOrder.getMchOrderNo());        // 商户订单号
        paramMap.put("channelId", payOrder.getChannelId() == null ? "" : payOrder.getChannelId());              // 渠道ID
        paramMap.put("amount", payOrder.getAmount() == null ? "" : payOrder.getAmount());                        // 支付金额
        paramMap.put("currency", payOrder.getCurrency() == null ? "" : payOrder.getCurrency());                 // 货币类型
        paramMap.put("status", payOrder.getStatus() == null ? "" : payOrder.getStatus());                    // 支付状态
        paramMap.put("clientIp", payOrder.getClientIp() == null ? "" : payOrder.getClientIp());                // 客户端IP
        paramMap.put("device", payOrder.getDevice() == null ? "" : payOrder.getDevice());                        // 设备
        paramMap.put("subject", payOrder.getSubject() == null ? "" : payOrder.getSubject());                        // 商品标题
        paramMap.put("channelOrderNo", payOrder.getChannelOrderNo() == null ? "" : payOrder.getChannelOrderNo()); // 渠道订单号
        paramMap.put("param1", payOrder.getParam1() == null ? "" : payOrder.getParam1());                        // 扩展参数1
        paramMap.put("param2", payOrder.getParam2() == null ? "" : payOrder.getParam2());                        // 扩展参数2
        paramMap.put("paySuccTime", payOrder.getPaySuccTime() == null ? "" : payOrder.getPaySuccTime());            // 支付成功时间
        paramMap.put("backType", backType == null ? "" : backType);
        // 先对原文签名
        String reqSign = PayDigestUtil.getSign(paramMap, resKey);
        paramMap.put("sign", reqSign);   // 签名
        // 签名后再对有中文参数编码
        try {
            paramMap.put("device", URLEncoder.encode(payOrder.getDevice() == null ? "" : payOrder.getDevice(), PayConstant.RESP_UTF8));
            paramMap.put("subject", URLEncoder.encode(payOrder.getSubject() == null ? "" : payOrder.getSubject(), PayConstant.RESP_UTF8));
            paramMap.put("param1", URLEncoder.encode(payOrder.getParam1() == null ? "" : payOrder.getParam1(), PayConstant.RESP_UTF8));
            paramMap.put("param2", URLEncoder.encode(payOrder.getParam2() == null ? "" : payOrder.getParam2(), PayConstant.RESP_UTF8));
        } catch (UnsupportedEncodingException e) {
            _log.error("URL Encode exception.pay", e);
            return null;
        }
        String param = PayUtil.genUrlParams(paramMap);
        StringBuffer sb = new StringBuffer();
        sb.append(payOrder.getNotifyUrl()).append("?").append(param);
        return sb.toString();
    }

    private String createNotifyUrl(RefundOrder refundOrder, String backType) {
        String mchId = refundOrder.getMchId();
        MchInfo mchInfo = mchInfoService.selectMchInfo(mchId);
        String resKey = mchInfo.getResKey();
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("payOrderId", refundOrder.getPayOrderId() == null ? "" : refundOrder.getPayOrderId());           // 支付订单号
        paramMap.put("mchId", refundOrder.getMchId() == null ? "" : refundOrder.getMchId());                        // 商户ID
        paramMap.put("mchOrderNo", refundOrder.getMchRefundNo() == null ? "" : refundOrder.getMchRefundNo());        // 商户订单号
        paramMap.put("channelId", refundOrder.getChannelId() == null ? "" : refundOrder.getChannelId());              // 渠道ID
        paramMap.put("amount", refundOrder.getRefundAmount() == null ? "" : refundOrder.getRefundAmount());                        // 支付金额
        paramMap.put("currency", refundOrder.getCurrency() == null ? "" : refundOrder.getCurrency());                 // 货币类型
        paramMap.put("status", refundOrder.getStatus() == null ? "" : refundOrder.getStatus());                    // 支付状态
        paramMap.put("clientIp", refundOrder.getClientIp() == null ? "" : refundOrder.getClientIp());                // 客户端IP
        paramMap.put("device", refundOrder.getDevice() == null ? "" : refundOrder.getDevice());                        // 设备
        paramMap.put("channelOrderNo", refundOrder.getChannelOrderNo() == null ? "" : refundOrder.getChannelOrderNo()); // 渠道订单号
        paramMap.put("param1", refundOrder.getParam1() == null ? "" : refundOrder.getParam1());                        // 扩展参数1
        paramMap.put("param2", refundOrder.getParam2() == null ? "" : refundOrder.getParam2());                        // 扩展参数2
        paramMap.put("refundSuccTime", refundOrder.getRefundSuccTime() == null ? "" : refundOrder.getRefundSuccTime());            // 支付成功时间
        paramMap.put("backType", backType == null ? "" : backType);
        // 先对原文签名
        String reqSign = PayDigestUtil.getSign(paramMap, resKey);
        paramMap.put("sign", reqSign);   // 签名
        // 签名后再对有中文参数编码
        try {
            paramMap.put("device", URLEncoder.encode(refundOrder.getDevice() == null ? "" : refundOrder.getDevice(), PayConstant.RESP_UTF8));
            paramMap.put("param1", URLEncoder.encode(refundOrder.getParam1() == null ? "" : refundOrder.getParam1(), PayConstant.RESP_UTF8));
            paramMap.put("param2", URLEncoder.encode(refundOrder.getParam2() == null ? "" : refundOrder.getParam2(), PayConstant.RESP_UTF8));
        } catch (UnsupportedEncodingException e) {
            _log.error("URL Encode exception.refund", e);
            return null;
        }
        String param = PayUtil.genUrlParams(paramMap);
        StringBuffer sb = new StringBuffer();
        sb.append(refundOrder.getNotifyUrl()).append("?").append(param);
        return sb.toString();
    }
}
